﻿using System;
using System.Collections.Concurrent;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;

namespace Xms.Abp.Caching;

public abstract class CacheProviderBase : ICacheProvider
{
    protected static readonly ConcurrentDictionary<Guid, ConcurrentDictionary<string, IXmsCache>> Caches = new();
    protected IAbpLazyServiceProvider LazyServiceProvider { get; }
    protected ICurrentTenant CurrentTenant
        => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
    protected virtual IConnectionStringResolver ConnectionStringResolver
        => LazyServiceProvider.LazyGetRequiredService<IConnectionStringResolver>();
    public CacheProviderBase(IAbpLazyServiceProvider lazyServiceProvider)
    {
        LazyServiceProvider = lazyServiceProvider;
    }

    public virtual async Task<IXmsCache> GetCacheAsync()
    {
        var connString = await GetConnectionStringAsync();
        var caches = Caches.GetOrAdd(CurrentTenant.Id ?? default, s => new());
        return GetOrAddCache(caches, connString);
    }

    protected abstract IXmsCache GetOrAddCache(ConcurrentDictionary<string, IXmsCache> caches, string connectionString);

    protected virtual string ConnectionStringName => "DefaultCache";

    public abstract string Name { get; }

    protected virtual async Task<string> GetConnectionStringAsync()
    {
        return await ConnectionStringResolver.ResolveAsync(ConnectionStringName);
    }
}

public abstract class CacheProviderBase<TCacheContext> : CacheProviderBase
    where TCacheContext : CacheContext<TCacheContext>
{
    public CacheProviderBase(IAbpLazyServiceProvider lazyServiceProvider) : base(lazyServiceProvider)
    {

    }

    private string _connectionStringName;

    protected override string ConnectionStringName
    {
        get
        {
            if (_connectionStringName == null)
            {
                _connectionStringName = typeof(TCacheContext).GetCustomAttribute<ConnectionStringNameAttribute>()?.Name?.RemovePostFix("Cache") ?? "Default";
                _connectionStringName += "Cache";
            }
            return _connectionStringName;
        }
    }

    protected override async Task<string> GetConnectionStringAsync()
    {
        // Multi-tenancy unaware contexts should always use the host connection string
        if (typeof(TCacheContext).IsDefined(typeof(IgnoreMultiTenancyAttribute), false))
        {
            using (CurrentTenant.Change(null))
            {
                return await ConnectionStringResolver.ResolveAsync(ConnectionStringName);
            }
        }

        return await ConnectionStringResolver.ResolveAsync(ConnectionStringName);
    }
}
